<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>爱心满屏飘字</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background-image: linear-gradient(0deg, #eee 1px, transparent 0),
                            linear-gradient(90deg, #eee 1px, transparent 0);
            background-size: 30px 30px;
            color: #333;
            min-height: 100dvh;
            overflow: hidden;
        }

        #board {
            position: relative;
            width: 100vw;
            height: 100dvh;
            overflow: hidden;
        }

        .card {
            position: absolute;
            width: 180px;
            height: 70px;
            border-radius: 8px 8px 12px 12px;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
            background: #fff;
            border: 1px solid rgba(0, 0, 0, 0.08);
            overflow: hidden;
            opacity: 0;
            transform-origin: center;
            transition: transform 0.35s ease, opacity 0.35s ease, left 0.35s ease,
                        top 0.35s ease, width 0.35s ease, height 0.35s ease,
                        border-radius 0.35s ease;
            z-index: 1;
        }

        .card.dragging {
            transition: none;
            box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);
        }

        .card.maximized {
            position: fixed;
            inset: 0;
            width: 100vw;
            height: 100vh;
            height: 100dvh;
            border-radius: 0;
            box-shadow: 0 28px 60px rgba(0, 0, 0, 0.4);
            z-index: 10000;
        }

        .card-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 6px 10px;
            background: rgba(255, 255, 255, 0.7);
            cursor: grab;
            user-select: none;
            touch-action: pan-y;
            height: 24px;
        }

        .card-header.dragging {
            cursor: grabbing;
        }

        .window-controls {
            display: flex;
            align-items: center;
            gap: 4px;
        }

        .window-controls .control {
            position: relative;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            border: 1px solid rgba(0, 0, 0, 0.08);
            background: #ccc;
            cursor: pointer;
            outline: none;
            padding: 0;
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }

        .window-controls .control.close {
            background: #ff5f57;
            border-color: #e0443e;
        }

        .window-controls .control.minimize {
            background: #febb2e;
            border-color: #dea123;
        }

        .window-controls .control.maximize {
            background: #28c840;
            border-color: #1aab2c;
        }

        .window-controls .control::after {
            content: '';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            opacity: 0;
            transition: opacity 0.2s ease;
        }

        .card-header:hover .window-controls .control::after {
            opacity: 0.8;
        }

        .window-controls .control.close::after {
            content: '×';
            width: auto;
            height: auto;
            background: none;
            font-size: 8px;
            line-height: 1;
            font-weight: 700;
            color: rgba(0, 0, 0, 0.7);
        }

        .window-controls .control.minimize::after {
            width: 5px;
            height: 1px;
            background: rgba(0, 0, 0, 0.6);
        }

        .window-controls .control.maximize::after {
            width: 5px;
            height: 5px;
            background: linear-gradient(
                45deg,
                rgba(0, 0, 0, 0.6) 0%,
                rgba(0, 0, 0, 0.6) 45%,
                transparent 45%,
                transparent 55%,
                rgba(0, 0, 0, 0.6) 55%,
                rgba(0, 0, 0, 0.6) 100%
            );
        }

        .card-title {
            font-size: 11px;
            font-weight: 600;
            color: rgba(0, 0, 0, 0.55);
            padding-left: 8px;
            flex: 1;
        }

        .card-body {
            padding: 8px 10px;
            font-size: 13px;
            line-height: 1.3;
            font-weight: 600;
            color: rgba(0, 0, 0, 0.72);
            word-break: break-word;
            overflow-wrap: anywhere;
            white-space: normal;
            height: calc(100% - 24px);
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            overflow: hidden;
        }

        .card.maximized {
            display: flex;
            flex-direction: column;
        }

        .card.maximized .card-title {
            display: none;
        }

        .card.maximized .card-body {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 100%;
            height: 100%;
            text-align: center;
            padding: clamp(32px, min(10vw, 10vh), 128px);
            padding-top: clamp(72px, min(14vw, 14vh), 192px);
            font-size: clamp(32px, min(12vw, 12vh), 120px);
            line-height: 1.05;
        }

        .heart-outline {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 600px;
            height: 540px;
            opacity: 0.05;
            pointer-events: none;
        }

        .heart-outline::before,
        .heart-outline::after {
            content: '';
            position: absolute;
            width: 300px;
            height: 480px;
            background: #ff6b81;
            border-radius: 300px 300px 0 0;
        }

        .heart-outline::before {
            left: 300px;
            transform: rotate(-45deg);
            transform-origin: 0 100%;
        }

        .heart-outline::after {
            left: 0;
            transform: rotate(45deg);
            transform-origin: 100% 100%;
        }

        @media (max-width: 768px) {
            .card {
                width: 160px;
                height: 60px;
            }

            .card-body {
                padding: 6px 8px;
                font-size: 12px;
            }

            .card-title {
                font-size: 10px;
            }
        }
    </style>
</head>
<body>
    <div id="board">
        <div class="heart-outline"></div>
    </div>

    <script>
        const board = document.getElementById('board');
        const messages = [
            "我想你了", "好好吃饭!", "记得加衣!", "天天开心", "期待下一次见面",
            "愿所有烦恼都消失", "记得照顾好自己", "别感冒", "世界上最后一个纯爱",
            "遇见你是浪漫的开始", "你是我生命中的光", "每一天都更爱你", "你值得所有美好",
            "我会一直陪着你", "你是我最珍贵的礼物", "爱你到永远", "你让我的世界更美好",
            "你是我的一切", "我的心属于你", "你是我最美的梦", "你的微笑是我的阳光",
            "与你相遇好幸运", "你是我每天的期待", "有你在身边真好", "爱你无需理由",
            "你是我心中的唯一", "想和你一起慢慢变老", "你是我最想留住的幸运",
            "爱你是我做过最好的事", "你是我温暖的港湾", "你让平凡的日子发光",
            "和你在一起的每一天都很甜", "你是我人生最美的风景", "爱你如初", "你是我全部的幸福",
            "你是我最甜蜜的负担", "有你的地方就是家", "爱你到天荒地老", "你是我唯一的心动",
            "你是我今生的挚爱", "爱你胜过千言万语", "你是我生命的意义", "我的世界因你而完整"
        ];

        const colors = [
            '#ffe0e3', '#c7f0ff', '#ffd8a8', '#d9f2d9', '#e5d7ff',
            '#f9f7d9', '#d2f0f8', '#ffd4f5', '#e3ffe3', '#ffe8d6'
        ];

        // 存储所有消息框的列表
        let cards = [];
        let heartPoints = [];
        let currentCardIndex = 0;
        let cycleIndex = 0; // 循环索引
        let cycleInterval;
        const MAXIMIZED_LAYER = 10000;
        let activeMaximizedCard = null;
        let zIndexCursor = 200;
        const cardStates = new WeakMap();

        // 生成爱心形状的点
        function generateHeartPoints(count) {
            const points = [];
            const radius = Math.min(window.innerWidth, window.innerHeight) * 0.35;
            const centerX = window.innerWidth / 2;
            const centerY = window.innerHeight / 2;

            for (let i = 0; i < count; i++) {
                const angle = (i * 2 * Math.PI) / count;

                // 爱心形状参数方程
                const x = 16 * Math.pow(Math.sin(angle), 3);
                const y = 13 * Math.cos(angle) - 5 * Math.cos(2*angle) - 2 * Math.cos(3*angle) - Math.cos(4*angle);

                // 缩放和平移
                const scaledX = centerX + x * radius / 16;
                const scaledY = centerY - y * radius / 16;

                points.push({ x: scaledX, y: scaledY });
            }

            return points;
        }

        function randomFrom(array) {
            return array[Math.floor(Math.random() * array.length)];
        }

        function applyTransform(card, state) {
            const scale = state.scale ?? 1;
            const angle = state.angle ?? 0;
            card.style.transform = `scale(${scale}) rotate(${angle}deg)`;
        }

        function bringToFront(card) {
            if (card === activeMaximizedCard) {
                card.style.zIndex = MAXIMIZED_LAYER;
                return;
            }

            zIndexCursor += 1;
            if (activeMaximizedCard && zIndexCursor >= MAXIMIZED_LAYER) {
                zIndexCursor = MAXIMIZED_LAYER - 1;
            }

            card.style.zIndex = zIndexCursor;
        }

        function closeCard(card) {
            const state = cardStates.get(card);
            if (!state || state.closing) return;
            if (card === activeMaximizedCard) {
                activeMaximizedCard = null;
            }
            state.closing = true;
            state.scale = 0.1;
            card.style.opacity = '0';
            applyTransform(card, state);

            const handleTransitionEnd = event => {
                if (event.propertyName === 'opacity') {
                    card.removeEventListener('transitionend', handleTransitionEnd);
                    card.remove();
                    // 从数组中移除
                    const index = cards.indexOf(card);
                    if (index > -1) {
                        cards.splice(index, 1);
                    }
                }
            };

            card.addEventListener('transitionend', handleTransitionEnd);
        }

        function toggleMaximize(card) {
            const state = cardStates.get(card);
            if (!state || state.closing) return;

            if (state.maximized) {
                restoreFromMaximize(card, state);
            } else {
                maximizeCard(card, state);
            }
        }

        function maximizeCard(card, state) {
            if (activeMaximizedCard && activeMaximizedCard !== card) {
                const activeState = cardStates.get(activeMaximizedCard);
                if (activeState) {
                    restoreFromMaximize(activeMaximizedCard, activeState);
                }
            }

            state.beforeMaximize = {
                left: state.left,
                top: state.top,
                scale: state.scale ?? 1,
                angle: state.angle ?? 0,
                width: card.offsetWidth,
                height: card.offsetHeight
            };

            card.classList.add('maximized');
            card.style.position = 'fixed';
            card.style.left = '0px';
            card.style.top = '0px';
            card.style.width = '100vw';
            card.style.height = '100dvh';
            card.style.borderRadius = '0';

            state.left = 0;
            state.top = 0;
            state.scale = 1;
            state.angle = 0;
            applyTransform(card, state);
            activeMaximizedCard = card;
            bringToFront(card);
            state.maximized = true;
        }

        function restoreFromMaximize(card, state) {
            const previous = state.beforeMaximize;
            if (!previous) return;

            card.classList.remove('maximized');
            card.style.position = 'absolute';
            card.style.left = `${previous.left}px`;
            card.style.top = `${previous.top}px`;
            card.style.width = `${previous.width}px`;
            card.style.height = `${previous.height}px`;
            card.style.borderRadius = '8px 8px 12px 12px';

            state.left = previous.left;
            state.top = previous.top;
            state.scale = previous.scale ?? 1;
            state.angle = previous.angle ?? state.angle ?? 0;
            applyTransform(card, state);
            state.maximized = false;
            if (activeMaximizedCard === card) {
                activeMaximizedCard = null;
            }
            bringToFront(card);
        }

        function startDrag(event, card) {
            const control = event.target.closest('.control');
            if (control) return;

            const state = cardStates.get(card);
            if (!state || state.closing || state.maximized) return;

            event.preventDefault();
            bringToFront(card);

            const header = card.querySelector('.card-header');
            card.classList.add('dragging');
            header.classList.add('dragging');

            state.dragging = true;
            state.dragOffsetX = event.clientX - state.left;
            state.dragOffsetY = event.clientY - state.top;

            let dragFrame = null;
            let pendingLeft = state.left;
            let pendingTop = state.top;

            const commitDrag = () => {
                dragFrame = null;
                const maxLeft = Math.max(window.innerWidth - card.offsetWidth, 0);
                const maxTop = Math.max(window.innerHeight - card.offsetHeight, 0);
                state.left = Math.max(pendingLeft, 0);
                state.top = Math.max(pendingTop, 0);
                card.style.left = `${state.left}px`;
                card.style.top = `${state.top}px`;
            };

            const handlePointerMove = moveEvent => {
                if (!state.dragging) return;

                pendingLeft = moveEvent.clientX - state.dragOffsetX;
                pendingTop = moveEvent.clientY - state.dragOffsetY;
                if (dragFrame === null) {
                    dragFrame = requestAnimationFrame(commitDrag);
                }
            };

            const handlePointerUp = () => {
                state.dragging = false;
                card.classList.remove('dragging');
                header.classList.remove('dragging');
                if (dragFrame !== null) {
                    cancelAnimationFrame(dragFrame);
                    commitDrag();
                }
                document.removeEventListener('pointermove', handlePointerMove);
                document.removeEventListener('pointerup', handlePointerUp);
            };

            document.addEventListener('pointermove', handlePointerMove);
            document.addEventListener('pointerup', handlePointerUp);
        }

        // 创建卡片的核心函数
        function createCard(point) {
            const card = document.createElement('div');
            card.className = 'card';

            const color = randomFrom(colors);
            const angle = (Math.random() - 0.5) * 8;
            const cardWidth = 180;
            const cardHeight = 70;

            card.style.background = color;
            card.style.left = `${point.x - cardWidth/2}px`;
            card.style.top = `${point.y - cardHeight/2}px`;
            card.style.opacity = '0';
            card.style.zIndex = ++zIndexCursor;

            card.innerHTML = `
                <div class="card-header">
                    <div class="window-controls">
                        <button class="control close" type="button" aria-label="关闭"></button>
                        <button class="control minimize" type="button" aria-label="最小化"></button>
                        <button class="control maximize" type="button" aria-label="最大化"></button>
                    </div>
                    <div class="card-title">爱你</div>
                </div>
                <div class="card-body">${randomFrom(messages)}</div>
            `;

            const state = {
                angle,
                scale: 0.7,
                left: point.x - cardWidth/2,
                top: point.y - cardHeight/2,
                maximized: false,
                closing: false
            };

            cardStates.set(card, state);
            applyTransform(card, state);
            board.appendChild(card);
            cards.push(card);

            // 添加事件监听
            const closeBtn = card.querySelector('.control.close');
            const maximizeBtn = card.querySelector('.control.maximize');
            const header = card.querySelector('.card-header');

            closeBtn.addEventListener('click', () => closeCard(card));
            maximizeBtn.addEventListener('click', () => toggleMaximize(card));
            header.addEventListener('dblclick', () => toggleMaximize(card));
            header.addEventListener('pointerdown', (e) => startDrag(e, card));

            // 动画显示
            setTimeout(() => {
                state.scale = 1;
                applyTransform(card, state);
                card.style.opacity = '1';
            }, 100);

            return card;
        }

        // 初始创建卡片
        function createInitialCard() {
            if (currentCardIndex >= heartPoints.length) {
                // 所有初始卡片创建完成后开始循环
                startHeartCycle();
                return;
            }

            const point = heartPoints[currentCardIndex];
            createCard(point);
            currentCardIndex++;

            // 继续创建下一个卡片
            setTimeout(createInitialCard, 200);
        }

        function startHeartCycle() {
            if (cycleInterval) clearInterval(cycleInterval);

            // 每0.5秒执行一次循环（1秒2个）
            cycleInterval = setInterval(() => {
                // 如果卡片数量超过爱心点数，移除最早的一个
                if (cards.length >= heartPoints.length) {
                    const oldestCard = cards[0];
                    if (oldestCard && oldestCard.parentElement) {
                        closeCard(oldestCard);
                    }
                }

                // 在爱心形状的下一个点创建新的消息框
                const point = heartPoints[cycleIndex % heartPoints.length];
                createCard(point);

                // 更新循环索引
                cycleIndex++;
            }, 500); // 每0.5秒循环一次（1秒2个）
        }

        // 初始化爱心形状
        function initHeartShape() {
            // 生成爱心形状的点 - 使用50个点
            heartPoints = generateHeartPoints(50);

            // 重置索引
            currentCardIndex = 0;
            cycleIndex = 0;

            // 开始创建初始卡片
            createInitialCard();
        }

        // 初始化
        window.addEventListener('load', initHeartShape);

        // 窗口大小改变时重新生成爱心
        window.addEventListener('resize', () => {
            // 清除现有卡片
            cards.forEach(card => {
                if (card.parentElement) {
                    card.remove();
                }
            });
            cards = [];

            if (cycleInterval) clearInterval(cycleInterval);

            initHeartShape();
        });
    </script>
</body>
</html>